﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using TelerikMvcGridCustomBindingHelper.Util;

namespace TelerikMvcGridCustomBindingHelper.Mapper
{
    public interface INullAlias
    {
        bool IsCaseInsensitive { get; }
        bool SearchExplicit { get; }
        IList<object> Aliases();
    }

    public class NullAliases : ConcurrentDictionary<NullAliasKey, INullAlias>
    {
        public bool HasAliasFor<TEntity, TViewModel>(GridPropertyInfo gridPropertyInfo, object convertedValue)
        {
            var hasAliasFor = ContainsKey(NullAliasKey.New(typeof(TEntity), typeof(TViewModel), gridPropertyInfo))
                   || ContainsKey(NullAliasKey.GlobalKey());

            if (hasAliasFor)
            {
                var nullAlias = GetAliasFor<TEntity, TViewModel>(gridPropertyInfo);

                if (nullAlias.Aliases().Any(o => Equals(o, convertedValue)))
                {
                    return true;
                }

                if (convertedValue != null && convertedValue.GetType().IsValueType)
                {
                    if (nullAlias.Aliases().Where(o => o != null && o.GetType().IsValueType).Any(o => Equals(Reflector.TryChangeType(o, convertedValue.GetType()), convertedValue)))
                    {
                        return true;
                    }
                }

                if (nullAlias.IsCaseInsensitive && gridPropertyInfo.PropertyType == typeof(string) && convertedValue != null)
                {
                    if (nullAlias.Aliases()
                        .Where(nullValue => nullValue != null)
                        .Any(nullValue => nullValue.ToString().ToUpper(GridModelMapper.ConfigurationObject.DefaultCulture)
                            .Equals(convertedValue.ToString().ToUpper(GridModelMapper.ConfigurationObject.DefaultCulture))))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public INullAlias GetAliasFor<TEntity, TViewModel>(GridPropertyInfo gridPropertyInfo)
        {
            INullAlias nullAlias;

            var tryGetValue = TryGetValue(NullAliasKey.New(typeof(TEntity), typeof(TViewModel), gridPropertyInfo), out nullAlias);

            if (tryGetValue == false)
                tryGetValue = TryGetValue(NullAliasKey.GlobalKey(), out nullAlias);

            if (tryGetValue == false)
                return null;

            return nullAlias;
        }
    }

    public struct NullAliasKey : IEquatable<NullAliasKey>
    {
        public readonly Type EntityType;
        public readonly Type ViewModelType;
        public readonly GridPropertyInfo GridPropertyInfo;

        private NullAliasKey(Type entityType, Type viewModelType, GridPropertyInfo gridPropertyInfo)
        {
            EntityType = entityType;
            ViewModelType = viewModelType;
            GridPropertyInfo = gridPropertyInfo;
        }

        public static NullAliasKey New(Type entityType, Type viewModelType, GridPropertyInfo gridPropertyInfo)
        {
            return new NullAliasKey(entityType, viewModelType, gridPropertyInfo);
        }

        public static NullAliasKey GlobalKey()
        {
            var type = typeof(INullAlias);
            var gpi = new GridPropertyInfo(type, type.FullName);
            return new NullAliasKey(type, type, gpi);
        }

        #region IEquatable<NullAliasKey>

        public bool Equals(NullAliasKey other)
        {
            return other.EntityType == EntityType
                   && other.ViewModelType == ViewModelType
                   && GridPropertyInfo != null
                   && (other.GridPropertyInfo.PropertyType == GridPropertyInfo.PropertyType
                   && Equals(other.GridPropertyInfo.PropertyPath, GridPropertyInfo.PropertyPath));
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof(NullAliasKey)) return false;
            return Equals((NullAliasKey)obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                var result = ToString().GetHashCode();

                if (EntityType != null) result = (result * 397) ^ EntityType.GetHashCode();

                if (ViewModelType != null) result = (result * 397) ^ ViewModelType.GetHashCode();

                if (GridPropertyInfo != null)
                {
                    if (GridPropertyInfo.PropertyPath != null)
                        result = (result * 397) ^ GridPropertyInfo.PropertyPath.GetHashCode();
                    if (GridPropertyInfo.PropertyType != null)
                        result = (result * 397) ^ GridPropertyInfo.PropertyType.GetHashCode();
                }

                return result;
            }
        }

        #endregion
    }
}